package com.john.booter.module.file.service.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.transaction.Transactional;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.john.booter.config.BooterSettings;
import com.john.booter.constant.StateType;
import com.john.booter.domain.entity.File;
import com.john.booter.exception.FileOptionException;
import com.john.booter.module.file.dao.FileDao;
import com.john.booter.module.file.service.FileService;

@Service
public class FileServiceImpl implements FileService {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	private BooterSettings setting; 
	@Resource
	private FileDao dao;
	
	@Override
	public File findById(String id) {
		return dao.findById(id);
	}

	@Override
	public String getPhysicalPath(String fileName) {
		File f = findById(FilenameUtils.getBaseName(fileName));
		String basePath = setting.getFileUploadPath();
		// 如果File未入库，预览的资源路径为temp路径
		if(f == null) {
			basePath = setting.getFileUploadTempPath();
		}
		if(StringUtils.isNotEmpty(f.getState())) {
			if(f.getState().equals(StateType.File.TEMP)) {
				basePath = setting.getFileUploadTempPath();
			}
		}
		String path = basePath + java.io.File.separator + fileName;
		return path;
	}
	
	@Transactional
	@Override
	public void saveTemp(File file) {
		file.setState(StateType.File.TEMP);
		dao.save(file);
	}

	@Transactional
	@Override
	public void save(File file) {
		dao.save(file);
	}

	@Override
	public List<File> saveEditorContentFile(String content) throws FileOptionException {
		List<File> files = extractFiles(content);
		copyFileFromTempPath(files);
		// 更新File表的状态
		if(CollectionUtils.isNotEmpty(files)) {
			for (File file : files) {
				dao.updateState(file.getId(), StateType.File.SAVED);
			}
		}
		return files;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<File> updateEditorContentFile(String content, String oldContent) throws FileOptionException {
		List<File> now = extractFiles(content);
		List<File> old = extractFiles(oldContent);
		
		// 求交集，复杂度是O(m+n)，m、n分别是两个list的元素个数
        Map<String, File> mapNow = new HashMap<String, File> ();
        Map<String, File> mapOld = new HashMap<String, File> ();
        Map<String, File> mapIntersection = new HashMap<String, File> ();
        for(File f: now) {
            mapNow.put(f.getId(), f);
        }
        
        String tempId;
        for(File f: old) {
        	mapOld.put(f.getId(), f);
            tempId = f.getId();
            if(mapNow.containsKey(tempId)) {
            	mapIntersection.put(tempId, f);
            }
        }
        
        Map<String, File> mapUnion = new HashMap<String, File>();
        mapUnion.putAll(mapOld);
        mapUnion.putAll(mapNow);
        
        Collection<String> addsKeys = CollectionUtils.subtract(mapNow.keySet(), mapIntersection.keySet());
        Collection<String> deletesKeys = CollectionUtils.subtract(mapOld.keySet(), mapIntersection.keySet());
        
        // now里有old里没有，即为需要新增的
        if(CollectionUtils.isNotEmpty(addsKeys)) {
    		List<File> adds = new ArrayList<File>();
    		for(String key: mapUnion.keySet()) {
    			if(addsKeys.contains(key)) {
    				adds.add(mapUnion.get(key));
    				dao.updateState(key, StateType.File.SAVED);
    			}
    		}
    		copyFileFromTempPath(adds);
        }
        
        // old里有now里没有，即为需要删除的，删除表和文件
        if(CollectionUtils.isNotEmpty(deletesKeys)) {
    		List<File> deletes = new ArrayList<File>();
    		for(String key: mapUnion.keySet()) {
    			if(deletesKeys.contains(key)) {
    				deletes.add(mapUnion.get(key));
    				dao.delete(key);
    			}
    		}
    		for (File f : deletes) {
    			java.io.File fileToDelete = new java.io.File(setting.getFileUploadPath() + java.io.File.separator + f.getSaveName());
				FileUtils.deleteQuietly(fileToDelete);
			}
        }
        
        return now;
	}

	/**
	 * file copy: temp path to eternal path
	 * @author weny
	 * @param files
	 */
	private void copyFileFromTempPath(List<File> files) {
		if(CollectionUtils.isNotEmpty(files)) {
			for (File file : files) {
				java.io.File srcFile = new java.io.File(setting.getFileUploadTempPath() + java.io.File.separator + file.getSaveName());
				java.io.File destDir = new java.io.File(setting.getFileUploadPath());
				try {
					FileUtils.moveFileToDirectory(srcFile, destDir, true);
				} catch (IOException e) {
					logger.error("Error occured when copy file from temp path", e);
					throw new FileOptionException("Error occured when copy file from temp path", e);
				}
			}
		}
	}

	/**
	 * extract img src from content str
	 * @author weny
	 * @param content
	 * @return
	 */
	private List<File> extractFiles(String content) {
		List<File> files = new ArrayList<File>();
		Document doc = Jsoup.parse(content);
		Elements images = doc.getElementsByTag("img");
		for (Element image : images) {
		  String src = image.attr("src");
		  String id = FilenameUtils.getBaseName(src);
		  File f = findById(id);
		  files.add(f);
		}
		return files;
	}

	@Transactional
	@Override
	public void clearTempFiles() {
		List<File> temps = dao.findByState(StateType.File.TEMP);
		if(CollectionUtils.isNotEmpty(temps)) {
			for (File f : temps) {
				java.io.File fileToDelete = new java.io.File(setting.getFileUploadTempPath() + java.io.File.separator + f.getSaveName());
				FileUtils.deleteQuietly(fileToDelete);
			}
		}
		dao.deleteByState(StateType.File.TEMP);
	}

}
